package com.autumn.platform.dao.schema.script.extend;

import java.util.regex.Pattern;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.ibatis.scripting.xmltags.DynamicContext;
import org.apache.ibatis.scripting.xmltags.OgnlCache;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.SimpleTypeRegistry;

import com.autumn.platform.dao.HandlerRegister;
import com.autumn.platform.dao.expression.IExpressionHandler;

public class ExpressionTextSqlNode extends TextSqlNode {

    private Configuration configuration;
    private String text;
    private Pattern injectionFilter;

    public ExpressionTextSqlNode(Configuration configuration, String text) {
        this(configuration, text, null);
    }

    public ExpressionTextSqlNode(Configuration configuration, String text, Pattern injectionFilter) {
        super(text, injectionFilter);
        this.configuration = configuration;
        this.text = text;
        this.injectionFilter = injectionFilter;
    }

    @Override
    public boolean apply(DynamicContext context) {
        BindingTokenParser handler = new BindingTokenParser(context, injectionFilter);
        GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
        context.appendSql(parser.parse(text));
        return true;
    }

    private class BindingTokenParser implements TokenHandler {

        private DynamicContext context;
        private Pattern injectionFilter;

        public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
            this.context = context;
            this.injectionFilter = injectionFilter;
        }

        @Override
        public String handleToken(String expression) {
            Object value = resolveExpression(expression.trim());
            String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
            checkInjection(srtValue);
            return srtValue;
        }

        private Object resolveExpression(String expression) {
            Object parameter = context.getBindings().get(DynamicContext.PARAMETER_OBJECT_KEY);
            //String databaseId = (String) context.getBindings().get(DynamicContext.DATABASE_ID_KEY);
            if (expression.startsWith("(") && expression.endsWith(")")) {
                expression = expression.substring(1, expression.length() - 1);
                IExpressionHandler handler = HandlerRegister.getHandlerRegistry().getExpressionHandler(expression, configuration);
                if (null != handler) {
                    return handler.eval(expression, parameter, configuration);
                }
                return null;
            } else {
                if (parameter == null) {
                    context.getBindings().put("value", null);
                } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
                    context.getBindings().put("value", parameter);
                }
                return OgnlCache.getValue(expression, context.getBindings());
            }
        }

        private void checkInjection(String value) {
            if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
                throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
            }
        }
    }
}
